﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Processing;
using Cloo;
using Processing.Structures;

namespace RayTracer.Kernels
{
    /// <summary>
    /// Farbaddition: Bei der Farbaddition gilt es auf Basis der Farbergebnisse und
    /// Richtungen der Folgestrahlen und auf Basis der Farbe, Richtungen und Sichtbarkeit
    /// der Lichtstrahlen den Farbwert für den aktuellen Strahl zu ermitteln.
    /// </summary>
    partial class KernelSumColor : AKernel
    {
        #region Attribute
        #region GPU
        private Buffer<Float4> gpu_eyeDir;
        private Buffer<Float4> gpu_reflCol;
        private Buffer<Float4> gpu_reflDir;
        private Buffer<float> gpu_reflDist;
        private Buffer<Float4> gpu_refrCol;
        private Buffer<Float4> gpu_refrDir;
        private Buffer<float> gpu_refrDist;
        private Buffer<Float4> gpu_normal;
        private Buffer<Float16> gpu_objMat;
        private Buffer<int> gpu_ligVis;
        private Buffer<Float4> gpu_ligDir;
        private Buffer<Float4> gpu_ligColor;

        private Buffer<Float4> gpu_color;
        #endregion GPU
        #endregion Attribute

        #region Eigenschaften
        #region Dateninitialisierung
        /// <summary>
        /// Gibt und setzt die Blickrichtungen.
        /// </summary>
        public Buffer<Float4> EyeDirections { get { return gpu_eyeDir; } set { gpu_eyeDir = value; } }

        /// <summary>
        /// Gibt und setzt die reflektierten Farben.
        /// </summary>
        public Buffer<Float4> ReflectionColors { get { return gpu_reflCol; } set { gpu_reflCol = value; } }

        /// <summary>
        /// Gibt und setzt die Reflektionsrichtungen.
        /// </summary>
        public Buffer<Float4> ReflectionDirections { get { return gpu_reflDir; } set { gpu_reflDir = value; } }

        /// <summary>
        /// Gibt und setzt die Reflektionsdistanzen.
        /// </summary>
        public Buffer<float> ReflectionDistances { get { return gpu_reflDist; } set { gpu_reflDist = value; } }

        /// <summary>
        /// Gibt und setzt die refraktierten Farben
        /// </summary>
        public Buffer<Float4> RefractionColors { get { return gpu_refrCol; } set { gpu_refrCol = value; } }

        /// <summary>
        /// Gibt und setzt die Refraktionsrichtungen.
        /// </summary>
        public Buffer<Float4> RefractionDirections { get { return gpu_refrDir; } set { gpu_refrDir = value; } }

        /// <summary>
        /// Gibt und setzt die Refraktionsdistanzen.
        /// </summary>
        public Buffer<float> RefractionDistances { get { return gpu_refrDist; } set { gpu_refrDist = value; } }

        /// <summary>
        /// Gibt und setzt die Normalen am Schnittpunkt.
        /// </summary>
        public Buffer<Float4> Normals { get { return gpu_normal; } set { gpu_normal = value; } }

        /// <summary>
        /// Gibt und setzt die Objektmaterialien.
        /// </summary>
        public Buffer<Float16> ObjectMaterials { get { return gpu_objMat; } set { gpu_objMat = value; } }

        /// <summary>
        /// Gibt und setzt die Lichtsichtbarkeiten.
        /// </summary>
        public Buffer<int> LightVisibilities { get { return gpu_ligVis; } set { gpu_ligVis = value; } }

        /// <summary>
        /// Gibt und setzt die Lichtrichtungen.
        /// </summary>
        public Buffer<Float4> LightDirections { get { return gpu_ligDir; } set { gpu_ligDir = value; } }

        /// <summary>
        /// Gibt und setzt die Lichtfarben.
        /// </summary>
        public Buffer<Float4> LightColors { get { return gpu_ligColor; } set { gpu_ligColor = value; } }

        /// <summary>
        /// Gibt die Ergebnisfarben an.
        /// </summary>
        public Buffer<Float4> ResultColors
        {
            get
            {
                if (State != KernelState.Done) throw new Exception("Die Kernel wurde noch nicht berechnet.");
                return gpu_color;
            }
        }
        #endregion Dateninitialisierung

        #region Kernelausführung
        /// <summary>
        /// Gibt den Quellcode des Programms an.
        /// </summary>
        protected override string ProgramSource { get { return KERNEL_SOURCE; } }

        /// <summary>
        /// Gibt den Namen des Programms an.
        /// </summary>
        protected override string ProgramName { get { return KERNEL_MAIN; } }

        /// <summary>
        /// Gibt die Dimension des Programms an.
        /// </summary>
        protected override long[] Dimensions { get { return new long[] { gpu_eyeDir.Length }; } }

        /// <summary>
        /// Gibt an ob die Kerneldaten valid initialisiert sind.
        /// </summary>
        protected override bool IsValidInitialized
        {
            get
            {
                if (gpu_eyeDir == null) throw new RayTracerException("Die Blickrichtungen müssen angegeben werden.");
                if (gpu_normal == null) throw new RayTracerException("Die Normalen müssen angegeben werden.");
                if (gpu_objMat == null) throw new RayTracerException("Die Objektmaterialien müssen angegeben werden.");
                if (gpu_ligVis == null) throw new RayTracerException("Die Lichtsichtbarkeiten müssen angegeben werden.");
                if (gpu_ligDir == null) throw new RayTracerException("Die Lichtrichtungen müssen angegeben werden.");
                if (gpu_ligColor == null) throw new RayTracerException("Die Lichtfarben müssen angegeben werden.");
                return
                    (gpu_reflCol == null || gpu_eyeDir.Length == gpu_reflCol.Length) &&
                    (gpu_reflDir == null || gpu_eyeDir.Length == gpu_reflDir.Length) &&
                    (gpu_refrCol == null || gpu_eyeDir.Length == gpu_refrCol.Length) &&
                    (gpu_refrDir == null || gpu_eyeDir.Length == gpu_refrDir.Length) &&
                    gpu_eyeDir.Length == gpu_normal.Length &&
                    gpu_eyeDir.Length == gpu_objMat.Length &&
                    gpu_eyeDir.Length * gpu_ligColor.Length == gpu_ligVis.Length &&
                    gpu_eyeDir.Length * gpu_ligColor.Length == gpu_ligDir.Length &&
                    gpu_eyeDir.Length > 0;
            }
        }
        #endregion Kernelausführung

        /// <summary>
        /// Gibt eine hochgegriffene Schätzung über die Anzahl der Bytes pro Pixel/Ray.
        /// </summary>
        public static int BytesPerPixel { get { return (8 * (sizeof(float) * 4)) + (sizeof(float) * 8); } }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Instanziiert und initialisiert die Kernel.
        /// </summary>
        /// <param name="processor">Der Prozessor.</param>
        public KernelSumColor(Processor processor) : base(processor) { }

        /// <summary>
        /// Führt Aktionen vor der Ausführung der Kernel - mit dem Run-Aufruf aus. (z. B. Datentransport von CPU zu GPU)
        /// </summary>
        protected override void PrepareKernelExecution()
        {
            base.PrepareKernelExecution();
            if (gpu_reflDir == null) gpu_reflDir = Processor.AllocateBuffer<Float4>(gpu_eyeDir.Length);
            if (gpu_refrDir == null) gpu_refrDir = Processor.AllocateBuffer<Float4>(gpu_eyeDir.Length);
            if (gpu_reflCol == null) gpu_reflCol = Processor.AllocateBuffer<Float4>(gpu_eyeDir.Length);
            if (gpu_refrCol == null) gpu_refrCol = Processor.AllocateBuffer<Float4>(gpu_eyeDir.Length);
            if (gpu_reflDist == null) gpu_reflDist = Processor.AllocateBuffer<float>(gpu_eyeDir.Length);
            if (gpu_refrDist == null) gpu_refrDist = Processor.AllocateBuffer<float>(gpu_eyeDir.Length);

            gpu_color = Processor.AllocateBuffer<Float4>(gpu_eyeDir.Length);
        }

        /// <summary>
        /// Setzt die Daten in Kernelargumente fest.
        /// </summary>
        protected override void SetArguments()
        {
            SetArgument(0, gpu_eyeDir);
            SetArgument(1, gpu_reflCol);
            SetArgument(2, gpu_reflDir);
            SetArgument(3, gpu_reflDist);
            SetArgument(4, gpu_refrCol);
            SetArgument(5, gpu_refrDir);
            SetArgument(6, gpu_refrDist);
            SetArgument(7, gpu_normal);
            SetArgument(8, gpu_objMat);
            SetArgument(9, gpu_ligVis);
            SetArgument(10, gpu_ligDir);
            SetArgument(11, gpu_ligColor);
            SetArgument(12, (int)(gpu_ligColor.Length));

            SetArgument(13, gpu_color, true);
        }
        #endregion Methoden
    }
}
